<?php

/**
 * SmartPHP framework
 *
 * @name SmartPHP framework
 * @package SmartPHP
 * @author 费尔 <admin@smartphp.cn>
 * @version 1.0 Beta
 * @link http://www.smartphp.cn
 * @copyright smartphp.cn
 * @license <a href="http://www.smartphp.cn">smartphp</a>
 */

namespace Base;

/**
 * 调试类
 */
class Debug {

    public static function error($error_no, $error_msg, $error_file, $error_line, $error_context) {
        $trace = debug_backtrace();

        $strlen = strlen(SMARTPHP_PATH);
        for ($index = 0; $index < count($trace); $index++) {
            if (!isset($trace[$index]['file']) || SMARTPHP_PATH === substr($trace[$index]['file'], 0, $strlen) || isset($trace[$index]['class']) && in_array(explode('\\', $trace[$index]['class'], 2)[0], ['Base', 'Help'])) {
                unset($trace[$index]);
            }
        }
        $trace = array_merge([['file' => $error_file, 'line' => $error_line]], $trace);

        self::obCleanup();

        if (!headers_sent()) {
            header('HTTP/1.0 503 Service Unavailable');
        }

        $error_type = Error::$errorTypes[$error_no] ?? $error_no;

        $error = [];
        $error_context = self::errorContext($error_context);

        $dbmsg = false;
        $error['msg'] = $error_msg;
        $trace = self::_trace($trace, __FILE__);

        $return = '';
        $h2_context = 'Vars in current scope: ' . count($error_context);

        $msg = $error['msg'];
        if (strlen($msg) > 100) {
            $msg = substr($msg, 0, 100) . '..';
        }
        $msg = self::escape($msg);
        $title = '<b>' . $error_type . ':</b> ' . $msg . ' in';
        $return .= self::header($error_type . ': ' . $msg . ' in', $trace);
        $return .= self::h1($title, $trace, 'error');


        $max_line = self::maxLine($trace);
        $return .= self::source($trace[0]['file'], $trace[0]['line'], strlen($max_line));

        $return .= self::pre($error_context, $h2_context, $max_lines = 12);
        $return .= self::printTrace($trace, strlen($max_line));
        if (Request::isPost() && count($_POST)) {
            $return .= self::h2('$_POST:');
            $return .= self::pre(self::postVars());
        }

        if (!empty(Log::$log['sql'])) {
            $return .= self::h2('SQL:');
            foreach (Log::$log['sql'] as $value) {
                $return .= self::query($value);
            }
        }
        $return .= self::footer();

        echo $return;

        exit();
    }

    public static function log() {
        $return = '';
        if (!empty(Log::$log)) {
            $return = '<div id="smartphp_page_trace" style="background:white;margin:6px;font-size:14px;border:1px dashed silver;padding:8px"><fieldset id="querybox" style="margin:5px;">';
            $return .= '<legend style="color:gray;font-weight:bold">LOG</legend><div style="overflow:auto;height:300px;text-align:left;">';
            foreach (Log::$log as $key => $value) {
                foreach ($value as $vo) {
                    $return .= $key . ':' . $vo . '<br/>';
                }
            }
            $return .= '</div></fieldset></div>';
        }
        return $return;
    }

    private static function query($query) {
        $query = self::queryColor($query);
        return self::pre($query, '', 12, 'white-space: normal; max-height: 178px; overflow: auto;');
    }

    private static function errorContext($context, $recursion = 1) {
        static $ds_max_elements = 128;
        static $ds_elements = 0;
        static $ds_max_len = 128;
        static $ds_data = 0;
        static $ds_max_data = 16384;

        if (memory_get_usage() > 1024 * 1024 * 10) {
            return '[debug-stop:memory-usage>10MB]';
        }
        $return = [];
        if ($recursion > 3) {
            return $return;
        }
        if (is_array($context)) {
            foreach ($context as $k => $v) {
                if (is_array($v)) {
                    if ('GLOBALS' === $k || '_' === substr($k, 0, 1)) {
                        continue;
                    }
                    $ds_elements++;
                    if ($ds_elements > $ds_max_elements) {
                        $return[] = '[debug-stop:' . $ds_max_elements . '-elements-reached]';
                        return $return;
                    }
                    $return[$k] = self::errorContext($context[$k], ++$recursion);
                } else if (is_string($v)) {
                    $v_len = strlen($v);
                    if ($v_len > $ds_max_len) {
                        $v = substr($v, 0, $ds_max_len) . '..[debug-stop:real-length:' . $v_len . ']';
                    }
                    $return[$k] = $v;
                    $ds_elements++;
                    $ds_data += $v_len;
                    if ($ds_elements > $ds_max_elements) {
                        $return[] = '[debug-stop:' . $ds_max_elements . '-elements-reached]';
                        return $return;
                    }
                    if ($ds_data > $ds_max_data) {
                        $return[] = '[debug-stop:max-data-reached:' . round($ds_max_data / 1024) . 'KB]';
                        return $return;
                    }
                } else {
                    $return[$k] = $v;
                    $ds_elements++;
                    if ($ds_elements > $ds_max_elements) {
                        $return[] = '[debug-stop:' . $ds_max_elements . '-elements-reached]';
                        return $return;
                    }
                }
            }
        }
        return $return;
    }

    private static function postVars() {
        $post = '';
        if (count($_POST)) {
            $passwords = ['password', 'pass', 'pwd', 'hash', 'md5'];
            foreach ($passwords as $pass) {
                foreach ($_POST as $k => $v) {
                    if (strstr(strtolower($k), $pass)) {
                        $_POST[$k] = '****';
                        $_REQUEST[$k] = '****';
                    }
                }
            }
            $post = $_POST;
            foreach ($post as $k => $v) {
                if (!is_string($v))
                    continue;
                if (strlen($v) > 100) {
                    $post[$k] = substr($v, 0, 100) . '..';
                }
            }
        }
        return $post;
    }

    private static function raw($var) {
        if (func_num_args() > 1)
            $var = func_get_args();
        echo '<pre>';
        var_dump($var);
        echo '</pre>';
        exit;
    }

    private static function obCleanup() {
        while (ob_get_level()) {
            ob_end_clean();
        }
        foreach (headers_list() as $header) {
            if (preg_match('/Content-Encoding:/i', $header)) {
                header('Content-encoding: none');
                return;
            }
        }
    }

    private static function reflection($trow) {
        ini_set('zend.ze1_compatibility_mode', 0);

        $name = '';
        if (isset($trow['class'])) {
            if (isset($trow['func'])) {
                try {
                    $reflect = new \ReflectionMethod($trow['class'], $trow['func']);
                } catch (\Exception $e) {
                    return 'Reflection failed';
                }
                $name = 'Method';
            } else {
                try {
                    $reflect = new \ReflectionClass($trow['class']);
                } catch (\Exception $e) {
                    return 'Reflection failed';
                }
                $name = 'Class';
            }
        } else {
            if (isset($trow['func'])) {
                try {
                    $reflect = new \ReflectionFunction($trow['func']);
                } catch (\Exception $e) {
                    return 'Reflection failed';
                }
                $name = 'Function';
            } else {
                return '';
            }
        }
        if ($reflect->isInternal()) {
            if (isset($trow['class']))
                return 'Internal class';
            else
                return 'Internal function';
        } else {
            $return = $name . ' ';
            $return .= 'defined in ';
            $return .= self::path($reflect->getFileName());
            $return .= ' on line ';
            $return .= $reflect->getStartLine();
            return $return;
        }
    }

    private static function header($title, $trace) {
        $title = strip_tags($title);
        $return = '';
        $return .= '<html><head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>' . $title . ' ' . strip_tags(self::shortfile($trace[0])) . '</title>
	<style type="text/css">
	body { margin:0em; background: #f0f0f0; }
	body,h1,h2 {}
	.font { font-size: 12px; font-family: Consolas, Courier New; }
	pre { background:#f0f0f0;margin:0em;padding:0.5em; }
	a,a:visited { color:#000099;text-decoration:none; }
	a:hover { text-decoration:underline; }
	h1, h2, h3 { margin: 0em; padding: 0em; font-weight: normal; }
	h3 { background: #EEEEFF; border-left: #808080 1px solid; border-bottom: #808080 1px solid; padding: 0.5em; }
	h1 { background: #666699; border-bottom: #333366 1px solid; border-top: #333366 1px solid; color: #fff; padding: 0.5em 0.5em; }
	h1.error { background: #E0006D; }
	h1 b { font-weight: normal; color: #FFFF00; }
	h2 b { font-weight: normal; }
	h2 a, h2 a:visited { color: #fff; display: block; }
	h2 a:hover { text-decoration: none; }
	.trace_call a, .trace_call a:visited { color: #000; display: block; }
	.trace_call a:hover { text-decoration: none; }
	.trace_call b { font-weight: normal; color: #0000ff; }
	.trace_call u { text-decoration: none; color: #0000ff; cursor: help; }
	.trace_content_last { border-bottom: #808080 1px solid; }
	.line-dblclicked { background: #ddd; }
	.line-even {}
	/* .line-odd { background: #f5f5f5; } */
	.line-over { background: #ddd; }
	.line-out {}
	.popmsg { border: #333366 1px solid; background: #EEEEFF; padding: 0.2em 0.5em; }
	</style>';

        $trace_count = count($trace) - 1;
        $return .= <<<SCRIPT
		<script>
		function $(id) { return document.getElementById(id); }
		function line_over(div)
		{
			if (!/ line-over/.test(div.className)) div.className += ' line-over';
			if (/ line-out/.test(div.className)) div.className = div.className.replace(/ line-out/, '');
		}
		function line_out(div)
		{
			if (!/ line-out/.test(div.className)) div.className += ' line-out';
			if (/ line-over/.test(div.className)) div.className = div.className.replace(/ line-over/, '');
		}
		function line_dblclick(div)
		{
			div.className= (/ line-dblclicked/.test(div.className)
				? div.className.replace(/ line-dblclicked/, '')
				: div.className+' line-dblclicked')
		}
		window.onload = function()
		{
			var els = document.getElementsByTagName('div');
			var length = els.length;
			for (var i = 0; i < length; i++) {
				if (els[i].className == 'line-even' || els[i].className == 'line-odd') {
					els[i].onmouseover = function() { line_over(this); }
					els[i].onmouseout = function() { line_out(this); }
					els[i].ondblclick = function() { line_dblclick(this); }
				}
			}
		}
		function toggle(id, from)
		{
			$(id).style.display = ($(id).style.display == 'none' ? '' : 'none');
			if (from) {
				from.blur();
			}
		}
		function toggle_pre(id, pre_id)
		{
			$('h2_'+id).blur();
			if (/\d+px/.test($(id).style.height)) {
				$(id).style.height = 'auto';
				$(id).style.overflow = 'visible';
			} else {
				var height;
				eval('height=max_lines_'+pre_id);
				$(id).style.height = height;
				$(id).style.overflow = 'auto';
			}
		}
		var trace_count = $trace_count;
		function toggle_trace_all()
		{
			var opened = false;
			for (var i = 0; i < trace_count; i++) {
				if ($('trace_content_'+i).style.display != 'none') {
					opened = true;
				}
			}
			for (var i = 0; i < trace_count; i++) {
				if ($('trace_content_'+i).innerHTML.length) {
					$('trace_content_'+i).style.display = opened ? 'none' : '';
				}
			}
			$('trace_h2').blur();
		}
		var popmsg_id = 0;
		var popmsg_map = {};
		function popmsg(msg, from_el)
		{
			var id, div_id;
			if (typeof popmsg_map[msg] != 'undefined') {
				id = popmsg_map[msg];
				div_id = 'popmsg_'+id;
			} else {
				id = popmsg_id++;
				popmsg_map[msg] = id;
				div_id = 'popmsg_'+id;
				var div = '<div id="'+div_id+'" class="popmsg font" style="display:none; position: absolute; z-index: 50000;">'+msg+'</div>';
				$('popmsg_container').innerHTML += div;
			}
			if (typeof from_el.onmouseout == 'undefined' || null === from_el.onmouseout) {
				from_el.onmouseout = function() { popmsg_out(div_id); };
			}
			var pos = popmsg_offset(from_el);
			var mpos = mousepos;
			var x = mpos[0] - pos[0];
			$(div_id).style.left = pos[0]+x+25+'px';
			$(div_id).style.top = pos[1]+from_el.offsetHeight+'px';
			$(div_id).style.display = 'block';
		}
		function popmsg_out(div_id)
		{
			$(div_id).style.display = 'none';
		}
		function popmsg_offset(el)
		{
			var x = 0, y = 0;
			do { if (el.nodeName.toLowerCase != 'td') { x += el.offsetLeft; y += el.offsetTop; }} while ((el = el.offsetParent) && el.nodeName.toLowerCase() != 'body');
			return [x,y];
		}
		var mousepos = [0,0];
		document.onmousemove = mousepos_listener;
		function mousepos_listener(e)
		{
			var posx = 0;
			var posy = 0;
			if (!e) e = window.event;
			if (e.pageX || e.pageY) 	{
				posx = e.pageX;
				posy = e.pageY;
			}
			else if (e.clientX || e.clientY) 	{
				posx = e.clientX + document.body.scrollLeft
					+ document.documentElement.scrollLeft;
				posy = e.clientY + document.body.scrollTop
					+ document.documentElement.scrollTop;
			}
			mousepos = [posx,posy];
		}

		</script>
SCRIPT;

        $return .= '</head><body><div id="popmsg_container"></div>';

        $font_detection_off = <<<SCRIPT
	<script>
	// must be inside BODY

	// taken from: http://www.lalit.org/lab/javascript-css-font-detect (Author: Lalit Patel)
	// comments removed & modified a little to make it work with all browsers
	var FontDetector = new function()
	{
		var h = document.getElementsByTagName("BODY")[0];
		var d = document.createElement("DIV");
		var s = document.createElement("SPAN");
		d.appendChild(s);
		d.style.fontFamily = "sans-serif";
		s.style.fontFamily = "sans-serif";
		s.style.fontSize   = "72px";
		s.innerHTML = "mmmmmmmmmml";
		h.appendChild(d);
		var defaultWidth = s.offsetWidth;
		var defaultHeight = s.offsetHeight;
		h.removeChild(d);
		this.detectionBroken = false;
		this.test = function(font)
		{
			if (this.detectionBroken) return false;
			h.appendChild(d);
			var f = [];
			f[0] = s.style.fontFamily = font+", sans-serif";
			f[1] = s.offsetWidth;
			f[2] = s.offsetHeight;
			h.removeChild(d);
			font = font.toLowerCase();
			if (font == "arial" || font == "sans-serif") {
				f[3] = true;
			} else {
				f[3] = (f[1] != defaultWidth || f[2] != defaultHeight);
			}
			return f[3];
		}
	}
	</script>
	<script>
	if (FontDetector.test('NoSuchFont') || !FontDetector.test('Courier New')) {
		FontDetector.detectionBroken = true;
	}
	if (FontDetector.test('ProFontWindows')) {
		//document.write('<style type="text/css">.font { font: 9px ProFontWindows; }</style>');
	} else if (FontDetector.test('ProggyTiny')) {
		//document.write('<style type="text/css">.font { font: 10px ProggyTiny; }</style>');
	}
	</script>
SCRIPT;

        return $return;
    }

    private static function footer() {
        return '</body></html>';
    }

    private static function h1($title, $trace, $cssclass = '', $more = '') {
        $title = strip_tags($title, '<b>');
        $return = '<h1 class="font ' . $cssclass . '">' . $title . ' ' . self::shortfile($trace[0]) . ($more ? '<br>' . $more : '') . '</h1>';
        return $return;
    }

    private static function h2($name) {
        $return = '<h2 class="font" style="background:#9999CC;color:#fff;padding:0.5em 0.5em;border-bottom: #333366 1px solid;border-top: #333366 1px solid;">' . $name . '</h2>';
        return $return;
    }

    private static function h2Toggle($name, $pre_id) {
// toggle for context vars
        $return = '<h2 class="font" style="background:#9999CC;color:#fff;padding:0.5em 0.5em;border-bottom: #333366 1px solid;border-top: #333366 1px solid;"><a href="javascript:void(0)" id="h2_pre_' . $pre_id . '" onclick="toggle_pre(\'pre_' . $pre_id . '\', ' . $pre_id . ')"><span style="float:right;" style="cursor:pointer;">[toggle]</span>' . $name . '</a></h2>';
        return $return;
    }

    private static function h2Trace($name) {
        $name = '<a href="javascript:void(0)" onclick="toggle_trace_all()" id="trace_h2"><span style="float:right;" style="cursor:pointer;">[toggle]</span>' . $name . '</a>';
        return self::h2($name);
    }

    private static function shortfile($tracerow) {
        $file = $tracerow['file'] ?? null;
        $line = $tracerow['line'] ?? null;
        if (!$file)
            $file = 'unknown file';
        if (!$line)
            $line = 0;
        $file = self::path($file);
        return sprintf('<b>%s</b> on line <b>%s</b>', $file, $line);
    }

    private static function path($file) {
        $root = str_replace('\\', DS, $_SERVER['DOCUMENT_ROOT']);
        $file = str_replace('\\', DS, $file);
        if ('/' == substr($root, -1) && strlen($root) > 1)
            $root = substr($root, 0, -1);
        $file = str_ireplace(dirname(SMARTPHP_PATH), '', $file);
        $file = str_ireplace(dirname(APP_DIRECTORY), '', $file);
        $file = str_ireplace(dirname(COMMON_PATH), '', $file);
        $file = str_ireplace($root, '', $file);
        $file = preg_replace('/[a-z]:/i', '', $file);
        return $file;
    }

    private static function pre($string, $h2_title = null, $max_lines = null, $style = '') {
        if (!is_string($string)) {
            $string = self::printVar($string);
        }
        static $pre_id = 0;
        $pre_id++;
        $count_lines = 0;
        if (strstr($string, '<div')) {
            $count_lines += substr_count($string, '<div');
        }
        if (preg_match('/<div[^<>]+class="line-[^<>]+>[^<>]+\n/', $string)) {
            $count_lines += substr_count($string, "\n");
        }
        if ($count_lines < $max_lines) {
            $max_lines = null;
        }
        $return = '';
        if ($max_lines) {
            $return .= '<pre class="font" id="max_lines_' . $pre_id . '">' . implode("\n", range(1, $max_lines)) . '</pre>';
            $return .= '<script>var max_lines_' . $pre_id . ' = $(\'max_lines_' . $pre_id . '\').offsetHeight; if (document.body.currentStyle && !window.opera) max_lines_' . $pre_id . ' -= 3; /* ie fix */ if (navigator.userAgent.indexOf(\'Firefox\') != -1) max_lines_' . $pre_id . ' -= 14; /* firefox fix 1 line too much */ $(\'max_lines_' . $pre_id . '\').style.display=\'none\';</script>';
        }
        if ($max_lines) {
            $return .= self::h2Toggle($h2_title, $pre_id);
        } else {
            if ($h2_title) {
                $return .= self::h2($h2_title);
            }
        }
        $style .= 'background: #f0f0f0;';
        if ($max_lines)
            $style .= 'overflow: auto;';
        $return .= '<pre class="font" style="' . $style . '" id="pre_' . $pre_id . '">' . $string . '</pre>';
        if ($max_lines) {
            $return .= '<script>$(\'pre_' . $pre_id . '\').style.height=(max_lines_' . $pre_id . ')+\'px\';$(\'pre_' . $pre_id . '\').style.overflow=\'auto\';</script>';
        }
        return $return;
    }

    private static function preTrace($string, $t_k, $trow) {
        if (isset($trow['file']) && file_exists($trow['file'])) {
            return '<pre class="trace_call font" style="background: #CFCFE5; border: #808080 1px solid; border-style: none none solid none;"><a href="javascript:void(0)" onclick="toggle(\'trace_content_' . $t_k . '\', this);"><span style="float:right;" style="cursor:pointer;">[toggle]</span>' . $string . '</a></pre>';
        } else {
            return '<pre class="trace_call font" style="background: #CFCFE5; border: #808080 1px solid; border-style: none none solid none;">' . $string . '</pre>';
        }
    }

    private static function maxLine($trace) {
        $max_line = null;
        foreach ($trace as $v) {
            if ($v['line'] > $max_line)
                $max_line = $v['line'];
        }
        return $max_line + 5;
    }

    private static function printTrace($trace, $line_width = null) {
        array_shift($trace);
        $return = '';
        $h2name = 'Backtrace: ' . (count($trace));
        if (count($trace)) {
            $return .= self::h2Trace($h2name);
            $max_string_length = 0;
            foreach ($trace as $k => $trow) {
                $string = str_pad($k + 1, $line_width, '0', STR_PAD_LEFT) . ': ' . self::shortfile($trow) . ' invokes:';
                if (strlen(strip_tags($string)) > $max_string_length)
                    $max_string_length = strlen(strip_tags($string));
            }
            $max_string_length++;

            $trace_string = '';
            $numrev = count($trace);

            foreach ($trace as $t_k => $trow) {

                $string = str_pad($numrev--, $line_width, '0', STR_PAD_LEFT) . ': ' . self::shortfile($trow) . ' invokes:';
                $string .= str_repeat(' ', $max_string_length - strlen(strip_tags($string)) + 1);
                $string .= '<u onmouseover="popmsg(\'' . self::reflection($trow) . '\', this)">';
                $string .= (isset($trow['class']) ? $trow['class'] . '.' : '');
                $string .= (isset($trow['func']) ? $trow['func'] : '');
                $string .= '</u>';
                $call_full = false;
                if (isset($trow['args'])) {
                    list($args_short, $call_full) = self::printArgsShort($trow['args']);
                    if (strlen($args_short) > 80) {
                        list($args_short, $call_full) = self::printArgsShort($trow['args'], true);
                        $call_full = true;
                    }
                    $string .= $args_short;
                }
                $trace_string .= self::preTrace($string, $t_k, $trow);
                $trace_string .= '<div style="border-bottom: #808080 1px solid;" id="trace_content_' . $t_k . '" ' . ($t_k == count($trace) - 1 ? 'class="trace_content_last"' : '') . '>';
                if ($call_full) {
                    $trace_string .= '<table cellspacing="0" cellpadding="0" width="100%" id="trace' . $t_k . '_source_container"><tr>';
                    $trace_string .= '<td width="80%" valign="top">';
                    $trace_string .= '<div id="trace' . $t_k . '_source">';
                }
                $trace_string .= self::source($trow['file'], $trow['line'], $line_width);
                if ($call_full) {
                    $trace_string .= '</div>';
                    $trace_string .= '</td>';
                    $trace_string .= '<td width="20%" valign="top">';
                    $trace_string .= '<h3 class="font" id="trace' . $t_k . '_args_full_head">Arguments full list: ' . count($trow['args']) . '</h3>';
                    $trace_string .= '<div id="trace' . $t_k . '_args_full" style="overflow: auto;">';

                    $trace_string .= '</div>';
                    $trace_string .= '</td></tr></table>';
                    $trace_string .= '<script>';
                    $trace_string .= '$(\'trace' . $t_k . '_args_full\').style.height=($(\'trace' . $t_k . '_source\').offsetHeight-$(\'trace' . $t_k . '_args_full_head\').offsetHeight)+\'px\';';
                    $trace_string .= '$(\'trace' . $t_k . '_source\').style.width=(($(\'trace' . $t_k . '_source_container\').offsetWidth/2)-10)+\'px\';';
                    $trace_string .= '</script>';
                }
                $trace_string .= '</div>';
                $trace_string .= '<script>$(\'trace_content_' . $t_k . '\').style.display=\'none\';</script>';
            }

            $return .= $trace_string;
        } else {
            $return .= self::h2($h2name);
        }
        return $return;
    }

    private static function printArgsShort($args, $cut = false) {
        $call_full = false;
        $ret = '(';
        foreach ($args as $k => $arg) {
            if ($k)
                $ret .= ', ';
            switch (true) {
                case is_string($arg):
                    $arg = self::escape($arg);
                    $ret .= "'";
                    if (substr($arg, -4) == '.php' || substr($arg, -4) == '.tpl') {
                        $arg = self::path($arg);
                    }
                    if ($cut && strlen($arg) > 20) {
                        $ret .= substr($arg, 0, 20) . '..';
                        $call_full = true;
                    } else {
                        $ret .= $arg;
                    }
                    $ret .= "'";
                    break;
                case is_array($arg):
                    $ret .= 'array(' . count($arg) . ')';
                    $call_full = true;
                    break;
                case is_object($arg):
                    $ret .= 'object(' . get_class($arg) . ')';
                    $call_full = true;
                    break;
                case is_int($arg):
                case is_float($arg):
                    $ret .= $arg;
                    break;
                case is_null($arg):
                    $ret .= 'null';
                    break;
                case is_bool($arg):
                    $ret .= ($arg ? 'true' : 'false');
                    break;
                case is_resource($arg):
                    $ret .= 'resource(' . get_resource_type($arg) . ')';
                    $call_full = true;
                    break;
                default:
                    $ret .= gettype($arg);
                    $call_full = true;
                    break;
            }
        }
        $ret .= ')';
        return [$ret, $call_full];
    }

    private static function source($file, $line, $line_width = null, $chunk_lines = 5) {
        $root = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']);
        if (file_exists($root . $file))
            $file = $root . $file;
        if (file_exists($file) && is_file($file) && is_readable($file)) {
            $lines = file($file);
            if (!isset($lines[$line - 1]))
                return null;

            $start = $line - 1 - $chunk_lines;
            if ($start < 0)
                $start = 0;
            $end = $line - 1 + $chunk_lines;
            if ($end > count($lines) - 1)
                $end = count($lines) - 1;

            $chunk = [];
            for ($i = $start; $i <= $end; $i++) {
                $chunk[$i + 1] = rtrim($lines[$i]);
            }
            foreach ($chunk as $k => $v) {
                if (trim($v))
                    break;
                else
                    unset($chunk[$k]);
            }
            unset($v);
            if (count($chunk)) {
                for ($k = $end + 1; $k > $start; $k--) {
                    if (trim($chunk[$k]))
                        break;
                    else
                        unset($chunk[$k]);
                }
            }

            $new_start = null;
            $new_end = null;
            foreach ($chunk as $l => $v) {
                $new_start = $l;
                break;
            }
            $chunk2 = $chunk;
            $chunk2 = array_reverse($chunk2, true);
            foreach ($chunk2 as $l => $v) {
                $new_end = $l;
                break;
            }

            $source = implode("\r\n", $chunk);
            $source = trim($source);

            $count = 0;
            if (preg_match_all("#\r\n#", $source, $match)) {
                $count = count($match[0]);
            }

            $range = range($new_start, $new_end);
            $line_str = $line;
            if ($line_width) {
                foreach ($range as $k => $v) {
                    $range[$k] = str_pad($v, $line_width, '0', STR_PAD_LEFT);
                    if ($line == $v)
                        $line_str = $range[$k];
                }
            } else {
                foreach ($range as $k => $v) {
                    if (strlen($v) < strlen($new_end)) {
                        $range[$k] = '0' . $v;
                        if ($line == $v)
                            $line_str = $range[$k];
                    }
                }
            }

            $num = '<code class="font" style="float:left; color: #808080; text-align: right; border-right: 1px solid #808080; background:#fff; padding:0.5em; padding-right:0.6em;">' . implode($range, '<br>') . '</code>';

            $highlight_line = '<span style="background:yellow;margin-right:-0.6em;margin-left:-0.5em;padding-right:0.6em;padding-left:0.5em;border-top: 1px solid #ccc;border-bottom: 1px solid #808080;">%s</span>';

            if (1 == count($range)) {
                $num = str_replace('>' . $line_str . '</code>', '>' . sprintf($highlight_line, $line_str) . '</code>', $num);
            } else {
                $num = str_replace($line_str . '<br>', sprintf($highlight_line, $line_str) . '<br>', $num);
            }

            $remove_phptag = false;
            if (!preg_match('#^\s*<' . '\?#', $source)) {
                $source = '<' . "?php\r\n" . $source;
                $remove_phptag = true;
            }
            $source = preg_replace('#([\'"][^\'"]*(password|pass|passwd|pwd|pw|hash|md5)[^\'"]*[\'"]\s*=>\s*[\'"])[^\'"]*([\'"])#i', '$1****$3', $source);
            $source = highlight_string($source, true);

            if ($remove_phptag) {
                $source = preg_replace('#<code[^<>]*>\s*<span[^<>]*>\s*<span[^<>]*>&lt;\?php\s*<br[^<>]*>(\s*</span>)?#i', '<code>', $source);
            }

            $source = preg_replace('#<code#i', '<code class="font" style="background:#f0f0f0; display:block; padding:0.5em; overflow: auto;"', $source);

            return $num . $source;
        }
    }

    private static function traceArgs($context, $recursion = 1) {
        static $ds_max_elements = 32;
        static $ds_elements = 0;
        static $ds_max_len = 128;
        static $ds_data = 0;
        static $ds_max_data = 4096;

        if (memory_get_usage() > 1024 * 1024 * 10) {
            return '[debug-stop:memory-usage>10MB]';
        }
        $return = [];
        if ($recursion > 3) {
            return $return;
        }
        if (is_array($context)) {
            foreach ($context as $k => $v) {
                if (is_array($v)) {
                    if ('GLOBALS' === $k || '_' === substr($k, 0, 1)) {
                        continue;
                    }
                    $ds_elements++;
                    if ($ds_elements > $ds_max_elements) {
                        $return[] = '[debug-stop:' . $ds_max_elements . '-elements-reached]';
                        return $return;
                    }
                    $return[$k] = self::errorContext($context[$k], ++$recursion);
                } else if (is_string($v)) {
                    $v_len = strlen($v);
                    if ($v_len > $ds_max_len) {
                        $v = substr($v, 0, $ds_max_len) . '..[debug-stop:real-length:' . $v_len . ']';
                    }
                    $return[$k] = $v;
                    $ds_elements++;
                    $ds_data += $v_len;
                    if ($ds_elements > $ds_max_elements) {
                        $return[] = '[debug-stop:' . $ds_max_elements . '-elements-reached]';
                        return $return;
                    }
                    if ($ds_data > $ds_max_data) {
                        $return[] = '[debug-stop:max-data-reached:' . round($ds_max_data / 1024) . 'KB]';
                        return $return;
                    }
                } else {
                    $return[$k] = $v;
                    $ds_elements++;
                    if ($ds_elements > $ds_max_elements) {
                        $return[] = '[debug-stop:' . $ds_max_elements . '-elements-reached]';
                        return $return;
                    }
                }
            }
        }
        return $return;
    }

    private static function _trace($trace, $ignore_file = null, $ignore_pattern = null) {
        $ret = [];
        foreach ($trace as $k => $v) {
            $current = [];
            $func = isset($v['function']) ? $v['function'] : '';
            $file = isset($v['file']) && strlen($v['file']) ? $v['file'] : '';
            if (isset($v['file']) && strlen($v['file'])) {
                $file = $v['file'];
                $__file = __FILE__;
                $file = str_replace('\\', '/', $file);
                $__file = str_replace('\\', '/', $__file);
                $ignore_file = str_replace('\\', '/', $ignore_file);
                if ($file == $__file) {
                    continue;
                }
                if ($file == $ignore_file) {
                    continue;
                }
                if ($ignore_pattern && preg_match($ignore_pattern, $file)) {
                    continue;
                }
                $line = isset($v['line']) ? $v['line'] : 0;
                $current['file'] = $file;
                $current['line'] = $line;
            } else {
                $current['file'] = 'unknown file';
                $current['line'] = '0';
            }
            if (isset($v['function'])) {
                $current['func'] = $v['function'];
            }
            if (isset($v['class']))
                $current['class'] = $v['class'];
            if (isset($v['args'])) {
                $current['args'] = self::traceArgs($v['args']);
            } else {
                $current['args'] = [];
            }
            if (count($current))
                $ret[] = $current;
        }
        if (!count($ret)) {
            $ret[] = array(
                'file' => 'unknown file',
                'line' => 0
            );
        }
        return $ret;
    }

    private static function printVar($var) {
        $max_line_length = 2005;


        if (func_num_args() > 1)
            $var = func_get_args();
        self::printVarStriplinest($var);
        $string = self::varDump($var);

        $string = preg_replace('#("]=>)\s+#', '$1', $string);
        $string = preg_replace('#(\[\d+\]=>)\s+#', '$1', $string);
        $string = preg_replace('#(\]=>)([^\s]+)\s("[^\r\n]*")([\r\n])#', '$1$3 $2$4', $string);
        $string = preg_replace('#\["([^"]*)"\]=>#', '$1: ', $string);
        $string = preg_replace('#\[(\d+)\]=>#', '$1: ', $string);
        $string = preg_replace('#[\r\n][ ]*}([\r\n])#', '$1', $string);
        //$string = preg_replace('#([\r\n])([ ]+)(\S)#e', "'\\1'.self::printVarDoubleindent('\\2').'\\3'", $string);
        $string = preg_replace('#\s+{([\r\n])#', '$1', $string);
        $string = preg_replace('#([\s])array\(0\)([\r\n])#', '$1array[0]$2', $string);
        $string = preg_replace('#[\s]array\(\d+\)([\r\n])#', '$1', $string);
        $string = preg_replace('#NULL([\r\n])#', 'null null$1', $string);
        $string = preg_replace('#(\&?int)\((\-?[\d]+)\)([\r\n])#', '$2 $1$3', $string);
        $string = preg_replace('#(\&?float)\((\-?[\d\.]+)\)([\r\n])#', '$2 $1$3', $string);
        $string = preg_replace('#(\&?bool)\((true|false)\)([\r\n])#', '$2 $1$3', $string);
        $string = preg_replace('#^array\(\d+\)[\r\n]+#', '', $string);
        $string = preg_replace('#\s+}\s*$#', '', $string);
        $string = preg_replace('#\t#', str_repeat(' ', 4), $string);

        $string = preg_replace("#^\"([^\r\n]*)\"[ ]([^\r\n]*)$#", '\\2 \\1', $string);
        $string = preg_replace("#^([^\r\n]+)[ ]\"([^\r\n]*)\"$#", '\\2 \\1', $string);
        $string = preg_replace("#^\"([^\r\n]*)\"([ ][^\r\n]+)$#", '\\1\\2', $string);
        if (preg_match('#^null null\s*$#', $string))
            return 'null';

        $string = trim($string);
        $lines = preg_split('#[\r\n]#', $string);
        $current_indent = 0;
        $last_indent = 0;
        $tomodify = [];
        $current_max2 = null;
        $current_max3 = null;

        foreach ($lines as $k => $line) {
            $line_pattern = "#^(\s*)([^:]+:[ ]*)[\"]([^\r\n]*)[\"]([ ][^\r\n]+)$#";
            $line_pattern2 = "#^(\s*)([^:]+:[ ]*)([^\r\n]*)([ ][^\r\n]+)$#";
            if (preg_match($line_pattern, $line, $match) || preg_match($line_pattern2, $line, $match)) {
                $current_indent = strlen($match[1]);

                $last_line = ($k == (count($lines) - 1));
                //if (defined('AAA')) {
                //echo '<pre>';
                //echo htmlspecialchars(print_r($lines,true));
                //exit;
                //}
                if (($current_indent != $last_indent && $k) || ($last_line)) {
                    if ($last_line && $current_indent == $last_indent) {
                        // @@@current_max@@@ : 1/2
                        if (strlen($match[2]) > $current_max2)
                            $current_max2 = strlen($match[2]);
                        if (strlen($match[3]) > $current_max3)
                            $current_max3 = strlen($match[3]);
                        $tomodify[$k] = $line;
                    }
                    // @@@tomodify@@@ : 1/3
                    foreach ($tomodify as $k2 => $line2) {
                        preg_match($line_pattern, $line2, $match2) || preg_match($line_pattern2, $line2, $match2);
                        $match2[4] = str_replace('array[0]', 'array(0)', $match2[4]);
                        if (strstr($match2[4], '(')) {
                            if (preg_match('#^\s*\(\d+\)$#', $match2[4])) {
                                // ex. object(ReflectionFunction)#1 (1)
                            } else {
                                $match2[4] = ' (' . trim(str_replace(array('(', ')'), array(':', ''), $match2[4])) . ')';
                            }
                        } else {
                            $match2[4] = ' (' . trim($match2[4]) . ')';
                        }
                        if ($current_max3 > $max_line_length)
                            $current_max3 = $max_line_length;
                        if (strlen($match2[3]) > $max_line_length) {
                            $match2[3] = urldecode($match2[3]);
                            $match2[3] = preg_replace("#([^\r\n]{" . $max_line_length . "," . $max_line_length . "})#", "\\1\r\n" . str_repeat(' ', $current_max2 + strlen($match2[1])), $match2[3]);
                        }
                        $lines[$k2] = $match2[1] . str_pad($match2[2], $current_max2) . str_pad($match2[3], $current_max3) . $match2[4];
                    }
                    $tomodify = [];
                    $current_max2 = null;
                    $current_max3 = null;
                }

                if ($last_line && $current_indent != $last_indent) {
                    // exception for last line with different indent
                    $tomodify = [];
                    $current_max2 = null;
                    $current_max3 = null;
                }

                // @@@current_max@@@ : 2/2
                if (strlen($match[2]) > $current_max2)
                    $current_max2 = strlen($match[2]);
                if (strlen($match[3]) > $current_max3)
                    $current_max3 = strlen($match[3]);
                $tomodify[$k] = $line;

                if ($last_line && $current_indent != $last_indent) {
                    // exception for last line with different indent
                    // @@@tomodify@@@ : 2/3
                    foreach ($tomodify as $k2 => $line2) {
                        preg_match($line_pattern, $line2, $match2) || preg_match($line_pattern2, $line2, $match2);
                        $match2[4] = str_replace('array[0]', 'array(0)', $match2[4]);
                        if (strstr($match2[4], '(')) {
                            if (preg_match('#^\s*\(\d+\)$#', $match2[4])) {
                                // ex. object(ReflectionFunction)#1 (1)
                            } else {
                                $match2[4] = ' (' . trim(str_replace(array('(', ')'), array(':', ''), $match2[4])) . ')';
                            }
                        } else {
                            $match2[4] = ' (' . trim($match2[4]) . ')';
                        }
                        if ($current_max3 > $max_line_length)
                            $current_max3 = $max_line_length;
                        if (strlen($match2[3]) > $max_line_length) {
                            $match2[3] = urldecode($match2[3]);
                            $match2[3] = preg_replace("#([^\r\n]{" . $max_line_length . "," . $max_line_length . "})#", "\\1\r\n" . str_repeat(' ', $current_max2 + strlen($match2[1])), $match2[3]);
                        }
                        $lines[$k2] = $match2[1] . str_pad($match2[2], $current_max2) . str_pad($match2[3], $current_max3) . $match2[4];
                    }
                }

                $last_indent = $current_indent;
            } else {
                $last_indent = -1;
                $last_line = ($k == (count($lines) - 1));
                if ($last_line) {
                    // If last line was empty, preg_match(line_pattern) failed
                    // @@@tomodify@@@ : 3/3
                    if (count($tomodify) > 1) {
                        foreach ($tomodify as $k2 => $line2) {
                            preg_match($line_pattern, $line2, $match2) || preg_match($line_pattern2, $line2, $match2);
                            $match2[4] = str_replace('array[0]', 'array(0)', $match2[4]);
                            if (strstr($match2[4], '(')) {
                                if (preg_match('#^\s*\(\d+\)$#', $match2[4])) {
                                    // ex. object(ReflectionFunction)#1 (1)
                                } else {
                                    $match2[4] = ' (' . trim(str_replace(array('(', ')'), array(':', ''), $match2[4])) . ')';
                                }
                            } else {
                                $match2[4] = ' (' . trim($match2[4]) . ')';
                            }
                            if ($current_max3 > $max_line_length)
                                $current_max3 = $max_line_length;
                            if (strlen($match2[3]) > $max_line_length) {
                                $match2[3] = urldecode($match2[3]);
                                $match2[3] = preg_replace("#([^\r\n]{" . $max_line_length . "," . $max_line_length . "})#", "\\1\r\n" . str_repeat(' ', $current_max2 + strlen($match2[1])), $match2[3]);
                            }
                            $lines[$k2] = $match2[1] . str_pad($match2[2], $current_max2) . str_pad($match2[3], $current_max3) . $match2[4];
                        }
                    }
                }
            }
        }
        foreach ($lines as $k => $line) {
            $cssclass = '';
            $cssclass = $k % 2 == 0 ? 'line-even' : 'line-odd';
            // onmouseover="if (!this.className.length) this.style.backgroundColor=\'#ddd\';"
            // onmouseout="if (!this.className.length) this.style.backgroundColor=\'#f0f0f0\'"
            // ondblclick =this.style.backgroundColor=\'#c5c5c5\';
            $lines[$k] = '<div class="' . $cssclass . '">' . self::ehtml($line) . '</div>';
        }
        $string = implode($lines, "");
        $string = preg_replace('#(password|pass|passwd|pwd|pw|hash|md5):\s+[^\r\n]{0,50}\s+\(string:\d+\)#Ui', '$1: ****', $string
        );
        return $string;
    }

    private static function printVarDoubleindent($s) {
        $s = preg_replace('#^[ ]{2,2}#', '', $s);
        return str_repeat($s, 2);
    }

    private static function printVarStriplinest(&$var, $recursion = 1) {
// change also recursion at self::traceArgs()
        if ($recursion > 3)
            return;
        if (is_array($var)) {
            foreach ($var as $k => $v) {
                if (is_array($v)) {
                    self::printVarStriplinest($var[$k], ++$recursion);
                } elseif (is_string($v)) {
                    $var[$k] = str_replace(["\r\n", "\n", "\r"], ['\r\n', '\n', '\r'], $var[$k]);
                }
            }
        } else if (is_string($var)) {
            $var = str_replace(["\r\n", "\n", "\r"], ['\r\n', '\n', '\r'], $var);
        }
    }

    private static function escapeOnce($s) {
        $s = str_replace(['&lt;', '&gt;', '&amp;lt;', '&amp;gt;'], ['<', '>', '&lt;', '&gt;'], $s);
        return str_replace(['&lt;', '&gt;', '<', '>'], ['&amp;lt;', '&amp;gt;', '&lt;', '&gt;'], $s);
    }

    private static function escape($html) {
        $html = str_replace(["\r\n", "\n", "\r"], ['\r\n', '\n', '\r'], $html);
        $html = self::ehtml($html);
        return $html;
    }

    private static function ehtml($html) {
        $undo = [
            '&amp;' => '&',
            '&lt;' => '<',
            '&gt;' => '>',
            '&quot;' => '"',
            '&#039;' => "'"
        ];
        $html = str_ireplace(array_keys($undo), array_values($undo), $html);
        return htmlspecialchars($html);
    }

    private static function printr($var) {
        if (func_num_args() > 1)
            $var = func_get_args();
        return print_r($var, true);
    }

    private static function varExport($var) {
        if (func_num_args() > 1)
            $var = func_get_args();
        return var_export($var, true);
    }

    private static function varDump($var) {
        if (func_num_args() > 1)
            $var = func_get_args();
//        return var_export($var,true);
        ob_start();
        if (!empty($var))
            var_dump($var);
        return ob_get_clean();
    }

    private static function queryColor($query) {
        $query = preg_replace('/(\w),(\w)/', '$1, $2', $query);
        $query = preg_replace('/\s+/', ' ', $query);
        $query = trim($query);

        $query = self::escapeOnce($query);

        $color = 'blue';

        $words = array('SELECT', 'UPDATE', 'DELETE', 'FROM', 'LIMIT', 'OFFSET', 'AND', 'INNER JOIN', 'OUTER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'JOIN', 'WHERE', 'SET', 'NAMES', 'ORDER BY', 'GROUP BY', 'GROUP', 'DISTINCT', 'COUNT', 'COUNT\(\*\)', 'IS', 'NULL', 'IS NULL', 'AS', 'ON', 'INSERT INTO', 'VALUES', 'BEGIN', 'COMMIT', 'CASE', 'WHEN', 'THEN', 'END', 'ELSE', 'IN', 'NOT', 'LIKE', 'ILIKE', 'ASC', 'DESC', 'LOWER', 'UPPER');

        $words = implode('|', $words);

        $nlwords = array('SELECT', 'UPDATE', 'DELETE', 'FROM', 'LIMIT', 'OFFSET', 'INNER JOIN', 'OUTER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'JOIN', 'WHERE', 'ORDER BY', 'GROUP_BY', 'GROUP', 'INSERT INTO', 'VALUES');
        $nlwords = implode('|', $nlwords);

        $query = preg_replace("#\s+({$nlwords})#", "\n" . '$1', $query);

        $query = preg_replace("#^({$words})(\s)#i", '<font color="' . $color . '">$1</font>$2', $query);
        $query = preg_replace("#(\s)({$words})$#i", '$1<font color="' . $color . '">$2</font>', $query);
// replace twice, some words when preceding other are not replaced
        $query = preg_replace("#([\s\(\),])({$words})([\s\(\),])#i", '$1<font color="' . $color . '">$2</font>$3', $query);
        $query = preg_replace("#([\s\(\),])({$words})([\s\(\),])#i", '$1<font color="' . $color . '">$2</font>$3', $query);
        $query = preg_replace("#^($words)$#i", '<font color="' . $color . '">$1</font>', $query);

        preg_match_all('#<font[^>]+>(' . $words . ')</font>#i', $query, $matches);
        foreach ($matches[0] as $k => $font) {
            $font2 = str_replace($matches[1][$k], strtoupper($matches[1][$k]), $font);
            $query = str_replace($font, $font2, $query);
        }

        //$query = nl2br($query);

        return $query;
    }

}
